/**
 * 
 */
package menus.demm;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.util.Vector;

import components.SetupExperiment;
import components.intertitles.BlockInstructions;
import components.intertitles.Trial;
import components.intertitles.TrialInstructions;

import fr.inria.insitu.noe.Platform;
import fr.lri.swingstates.canvas.CEllipse;
import fr.lri.swingstates.canvas.CPolyLine;
import fr.lri.swingstates.canvas.CSegment;
import fr.lri.swingstates.canvas.CShape;
import fr.lri.swingstates.canvas.CStateMachine;
import fr.lri.swingstates.canvas.Canvas;
import fr.lri.swingstates.gestures.Gesture;
import menus.common.AbstractItem;
import menus.common.AbstractMenu;
import menus.common.AdHocGestureAnalyzer;
import menus.common.statemachines.OneStrokeStateMachine;

/**
 * A Distance Augmented Marking Menu. Read my article...
 * 
 * @author mathieu
 */
public class DEMM extends AbstractMenu {
	
	/*
	 * 		- Polychrome mark	.	.	.	.	.	DONE
	 * 		- Ideal mark	.	.	.	.	.	.	DONE
	 * 		- Correct ring widht...	.	.	.	.	DONE
	 */
	
	/**
	 * The "center" radius
	 */
	protected static final int CenterRadius = 15;
	
	/**
	 * The maximum radius
	 */
	protected static final int MaxRadius = 10000;
	
	/**
	 * The ring widths, depending on their amount
	 */
	protected static final double[][] ringRadiuses = 
	{
		{CenterRadius, MaxRadius},
		{CenterRadius, 109.0, MaxRadius},	
		{CenterRadius, 78.5, 189.5, MaxRadius},	
		{CenterRadius, 69.5, 160.0, 316.5, MaxRadius},	
		{CenterRadius, 59.0, 129.5, 244.0, 449.0, MaxRadius}	
	};
	
	/**
	 * Colors for each level
	 */
	protected static final Color[] colors = new Color[]{
			Color.blue,
			Color.green,
			Color.red,
			Color.black,
			Color.magenta
		};
	
	/**
	 * The angular width of an item depending on its ring
	 */
	protected double[] sliceAngle;
	
	/**
	 * Amount of rings in this menu
	 */
	protected int nbRings;
	
	/**
	 * Used for the experiment.
	 */
	protected double markLength;
	
	/**
	 * Used also in the experiment
	 */
	protected double strokeAngle;
	
	protected DEMMStateMachine stateMachine;
	
	
	
	// ----------------------------------------------------------------- XXX

	
	
	/**
	 * 
	 * @param canvas
	 * @param itemLabels TODO
	 */
	public DEMM(Canvas canvas, String[][] itemLabels) {
		
		super(canvas, itemLabels);
		
		this.stateMachine = new DEMMStateMachine(canvas, this, ringRadiuses[itemLabels.length-1]);
		//this.setStateMachine(new OneStrokeStateMachine(canvas, this));
		
		nbRings = itemLabels.length-1;
		
		analyzer = new AdHocGestureAnalyzer();
		
	}
	
	/**
	 * Initializing the slice angular widths
	 */
	@Override
	protected void init(Canvas canvas, String[][] itemLabels) {
		
		sliceAngle = new double[itemLabels.length];
		for (int i = 0; i < itemLabels.length; i++){
			sliceAngle[i] = 2*Math.PI / itemLabels[i].length;
		}
	}
	
	@Override
	public AbstractItem getItem(Gesture gesture) {
		
		// Simplifying the gesture
		Vector<Point2D> path = ((AdHocGestureAnalyzer)analyzer).adHocAnalyse(gesture);
		
		// Checking if it's straight
		if (path.size() == 2){
			
			// Finding the ring
			markLength = path.get(0).distance(path.get(1));
			
			// They don't have the same width, so we must check until we find
			int ring = -1;
			while (ringRadiuses[items.length-1][ring+1] < markLength){
				ring++;
			}
			
			// ring=-1 means the stroke was too short
			if (ring > -1){
				
				// Finding the angle
				strokeAngle = Math.atan2(
						path.get(0).getY() - path.get(1).getY(), 
						path.get(1).getX() - path.get(0).getX()
				);
				
				// Keeping it positive
				if (strokeAngle < 0)
					strokeAngle += 2*Math.PI;
				
				int angleIndex = getAngleIndex(ring, strokeAngle);
				
				System.out.println("\n\t\tSELECTED : " + (ring * items[ring].length + angleIndex) + "\n");
				
				shapeError = false;
				
				return items[ring][angleIndex];
				
			}
			
		}
		else
			shapeError = true;
		
		return null;
		
	}
	
	/**
	 * @param angle
	 * @param ring
	 * @return The item index corresponding to this angle in this ring
	 */
	protected int getAngleIndex(int ring, double strokeAngle){
		return new Double(Math.floor( strokeAngle / sliceAngle[ring] + .5 )).intValue() % items[ring].length;
	}

	@Override
	protected DEMMItem createItem(String[][] itemLabels, int i, int j, String tag) {
		
		double startAngle = j * sliceAngle[i];
		double startRadius = ringRadiuses[itemLabels.length-1][i];
		double endRadius = ringRadiuses[itemLabels.length-1][i+1];
		
		return new DEMMItem(this, i, itemLabels[i][j], sliceAngle[i], startAngle, startRadius, endRadius, tag);
	}
	
	public double[] getFrontiers(){
		return ringRadiuses[nbRings];
	}
	
	public Color[] getColors(){
		return colors;
	}

	
	// ----------------------------------------------------------------------------------

	
	@Override
	protected void setLengthError() {
		
		double frontier1 = ringRadiuses[nbRings][targetIndex/8];
		double frontier2 = ringRadiuses[nbRings][targetIndex/8+1];
		
		if (markLength < frontier1){
			lengthError = markLength - frontier1;
		}
		else if (markLength > frontier2){
			lengthError = markLength - frontier2;
		}
		else{
			lengthError = 0;
		}
	}

	@Override
	protected void setOrientationError() {
		
		orientationError = strokeAngle - (targetIndex % 8) * sliceAngle[targetIndex/8];
		
		if (orientationError < -Math.PI)
			orientationError += 2 * Math.PI;
		else if (orientationError > Math.PI)
			orientationError -= 2 * Math.PI;
		
	}

	@Override
	protected void setShapeError() {
		
		// Already done
		
	}
	
	@Override
	protected void setPositionError() {
		
		positionError = 0;
		
	}

	@Override
	public void drawInstructions(Canvas canvas, CShape zone, String... tags) {
		//drawInstructions_numbered(canvas, zone, tags);
		//drawInstructions_squares(canvas, zone, tags);
		drawInstructions_circles(canvas, zone, tags);
	}
	
	public void drawInstructions_circles(Canvas canvas, CShape zone, String... tags) {
		
		int ringIndex = targetIndex/8;
		double orientation = sliceAngle[ringIndex] * (targetIndex%8);
		
		int circleRadius = 100;
		int ringWidth;
		
		for (int r = nbRings; r >= 0; r--){
			
			ringWidth = (r+1) * circleRadius / nbRings;
			CEllipse ring = (CEllipse)canvas.newEllipse(zone.getCenterX() - ringWidth/2, zone.getCenterY() - ringWidth/2, ringWidth, ringWidth)
											.setStroke(new BasicStroke(2))
											.setOutlinePaint(Color.white)
											.setFilled(false)
											
											.addTag(TrialInstructions.InstructionTag)
											
											.setPickable(false)
											.setDrawable(true);
			
			if (r <= ringIndex){
				
				canvas.newPolyLine(0, 0)
					.lineTo(1.5* circleRadius * Math.cos(sliceAngle[r]/2), 1.5* circleRadius * Math.sin(sliceAngle[r]/2))
					.lineTo(1.5* circleRadius * Math.cos(-sliceAngle[r]/2), 1.5* circleRadius * Math.sin(-sliceAngle[r]/2))
					
					.setOutlined(false)
					.setFilled(true)
					.setFillPaint(colors[r])
					
					.setReferencePoint(0, .5)
					.translateTo(zone.getCenterX(), zone.getCenterY())
					.rotateBy(-orientation)
					
					.setClip(ring)
					
					.addTag(TrialInstructions.InstructionTag)
					
					.setPickable(false)
					.setDrawable(true);
				
			}
			
		}
		
	}
	
	public void drawInstructions_squares(Canvas canvas, CShape zone, String... tags) {
		
		super.drawInstructions(canvas, zone, tags);
		
		int ring = targetIndex/8;
		double orientation = sliceAngle[ring] * (targetIndex%8);
		
		int squareSize = 20;
		int squaresGap = 5;
		
		int distance;
		
		for (int r = 0; r < ring; r++){
			
			distance = 30 + r * (squaresGap + squareSize);
			
			canvas.newRectangle(0, 0, squareSize, squareSize/2)
				.setOutlined(false)
				.setFilled(true)
				.setFillPaint(colors[r])
				
				.setReferencePoint(.5, .5)
				//.rotateBy(orientation)
				.translateTo( (zone.getCenterX() + distance * Math.cos(orientation)),  (zone.getCenterY() - distance * Math.sin(orientation)))
				.rotateBy(-orientation)
				
				.addTag(TrialInstructions.InstructionTag)
				
				.setPickable(false)
				.setDrawable(true);
		}
		
		distance = 30 + ring * (squaresGap + squareSize);
		
		canvas.newPolyLine(0, -squareSize/2)
			.lineTo(squareSize, 0)
			.lineTo(0, squareSize/2)
			.close()
			
			.setOutlined(false)
			.setFilled(true)
			.setFillPaint(colors[ring])
			
			.setReferencePoint(.5, .5)
			.translateTo( (zone.getCenterX() + distance * Math.cos(orientation)),  (zone.getCenterY() - distance * Math.sin(orientation)))
			.rotateTo(-orientation)
			
			.addTag(TrialInstructions.InstructionTag)
			
			.setPickable(false)
			.setDrawable(true);
		
	}
	
	public void drawInstructions_numbered(Canvas canvas, CShape zone, String... tags) {
		
		super.drawInstructions(canvas, zone, tags);
		
		int ring = targetIndex/8;
		double orientation = sliceAngle[ring] * (targetIndex%8);
		
		double scale = 30;
		double distance = 50;
		
		// Triangle
		
		canvas.newPolyLine(0, -scale/2)
			.lineTo(scale, 0)
			.lineTo(0, scale/2)
			.close()
			
			.setOutlined(false)
			.setFilled(true)
			.setFillPaint(colors[ring])
			
			.setReferencePoint(0, .5)
			.translateTo( (zone.getCenterX() + distance * Math.cos(orientation)),  (zone.getCenterY() - distance * Math.sin(orientation)))
			.rotateTo(-orientation)
			
			.addTag(TrialInstructions.InstructionTag)
			
			.setPickable(false)
			.setDrawable(true);
		
		// Ring index
		
		canvas.newText(0, 0, new Integer(ring+1).toString())
			.setReferencePoint(.5, .5)
			.rotateTo(orientation)
			.translateTo( (zone.getCenterX() + distance/2 * Math.cos(orientation)),  (zone.getCenterY() - distance/2 * Math.sin(orientation)))
			.rotateBy(-orientation)
			.scaleBy(2)
			
			
			.addTag(TrialInstructions.InstructionTag)
			
			.setPickable(false)
			.setDrawable(true);
		
	}
	
	public void drawInstructions_scaled(Canvas canvas, CShape zone, String... tags) {
		
		super.drawInstructions(canvas, zone, tags);
		
		int ring = targetIndex/8;
		double orientation = sliceAngle[ring] * (targetIndex%8);
		
		// double scale = zone.getWidth() * .8 / ringRadiuses[4][4];
		double scale = .5;
		
		for (int r = 0; r < ring; r++){
			canvas.newSegment(0, 0, scale * (ringRadiuses[nbRings][r+1] - ringRadiuses[nbRings][r]), 0)
				.setStroke(new BasicStroke(3))
				.setOutlinePaint(colors[r])
				.setFilled(false)
				
				.setReferencePoint(0, 0)
				.translateTo( (zone.getCenterX() + scale * ringRadiuses[nbRings][r]*Math.cos(orientation)),  (zone.getCenterY() - scale * ringRadiuses[nbRings][r]*Math.sin(orientation)))
				.rotateTo(-orientation)
				
				.setClip(zone)
				.addTag(TrialInstructions.InstructionTag)
				
				.setDrawable(true)
				.setPickable(false);
		}
		
		canvas.newSegment(0, 0, scale * (ringRadiuses[nbRings][ring+1] - ringRadiuses[nbRings][ring]) / 2, 0)
			.setStroke(new BasicStroke(3))
			.setOutlinePaint(colors[ring])
			.setFilled(false)
			
			.setReferencePoint(0, 0)
			.translateTo( (zone.getCenterX() + scale * ringRadiuses[nbRings][ring]*Math.cos(orientation)),  (zone.getCenterY() - scale * ringRadiuses[nbRings][ring]*Math.sin(orientation)))
			.rotateTo(-orientation)
			
			.setClip(zone)
			.addTag(TrialInstructions.InstructionTag)
			
			.setDrawable(true)
			.setPickable(false);
		
	}

	@Override
	public boolean isSelectionEnded() {
		return stateMachine.isSelectionEnded();
	}

	@Override
	public void resetSelection() {
		stateMachine.resetSelection();
	}

	@Override
	protected void setSelectionDuration() {
		selectionDuration = stateMachine.getSelectionTimes()[1];
	}

	@Override
	protected void setStartTime() {
		startTime = stateMachine.getSelectionTimes()[0];
		
	}
	
	@Override
	public DEMMStateMachine getStateMachine() {
		return stateMachine;
	}
	
	public static void drawMenu() {
		
		double ringWidth;
		int nbRings = (Integer)Platform.getInstance().getFactorValue(Trial.Capacity) / 8 ;
		Canvas canvas = ((SetupExperiment)Platform.getExperiment().getSetUp()).getGestureArea();
		double sliceAngle = Math.PI / 4;
		
		for (int r = nbRings; r >= 0; r--){
			
			ringWidth = 2*ringRadiuses[nbRings-1][r];
			
			canvas.newEllipse(canvas.getCenterX() - ringWidth/2, canvas.getCenterY() - ringWidth/2, ringWidth, ringWidth)
				.setStroke(new BasicStroke(1))
				.setOutlinePaint(Color.white)
				.setFilled(true)
				.setFillPaint( (r >= 1) ? colors[r-1] : Color.white )
				
				.addTag(BlockInstructions.InstructionTag)
				
				.setAntialiased(true)
				
				.setPickable(false)
				.setDrawable(true);
			
		}
		
		for (double o = 0; o < 2*Math.PI; o += Math.PI/4){
			
			canvas.newSegment(0, 0, 2000, 0)
				
				.setOutlined(true)
				.setOutlinePaint(Color.white)
				.setStroke(new BasicStroke(1))
				
				.setReferencePoint(0, .5)
				.translateTo(canvas.getCenterX(), canvas.getCenterY())
				.rotateBy(-o + sliceAngle/2)
				
				.setAntialiased(true)
				
				.addTag(BlockInstructions.InstructionTag)
				
				.setPickable(false)
				.setDrawable(true);
			
		}
	}
	
	

}
